package codemaster.domain;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import codemaster.core.Helper;

import com.Ostermiller.util.ExcelCSVParser;

import common.util.FileUtility;
import common.util.StringUtility;

public class DomainObjectBuilder
{
	private static final String YES = "Y";

	private static final int C_NAME = 0;

	private static final String[] imports = { "import java.io.Serializable;\n", "import java.math.BigDecimal;\n", "import java.sql.Timestamp;\n",
	        "import java.util.Date;\n", "import com.game.dbpersistence.BeanFather;\n",
	        "import com.game.core.dbwrite.server.DBService;\n", "import java.io.IOException;\n",
	        "import java.lang.ClassNotFoundException;\n", "import java.io.ByteArrayInputStream;\n", "import java.io.ByteArrayOutputStream;\n",
	        "import java.io.ObjectInputStream;\n", "import java.io.ObjectOutputStream;\n" };

	/**
	 * Builds a domain object class file
	 * 
	 * @param header
	 *            File header
	 * @param inputFile
	 *            the input file
	 * @param outputDir
	 *            the output directory
	 * @param packageName
	 *            the package name
	 */
	public void buildBean(String header, File inputFile, File outputDir, String packageName)
	{
		// Determines the class name from the input file name
		String className = StringUtility.toTitleCase(FileUtility.getFileNameWithoutExtension(inputFile));

		BufferedWriter bfw = null;
		BufferedReader bfr = null;
		try
		{
			// Reads domain object attributes
			FileInputStream f = new FileInputStream(inputFile);
			Reader r = new InputStreamReader(f, "utf8");
			bfr = new BufferedReader(r);
			String[][] table = ExcelCSVParser.parse(bfr);
			bfr.close();

			// Creates the file at the destination folder
			String packageDir = Helper.getPackageDir(outputDir, packageName);
			(new File(packageDir)).mkdirs();
			bfw = new BufferedWriter(new FileWriter(packageDir + File.separator + className + ".java"));

			// Write header
			header = header.replaceAll("<class>", className);
			bfw.write(header);

			// Writes package
			bfw.write("package " + packageName + ";\n");
			bfw.write("\n");

			// Writes import
			bfw.write(getImport(table, "Bean"));
			bfw.write("\n");

			// Writes class
			bfw.write("/**\n");
			bfw.write(" * Domain object class for " + className + "\n");
			bfw.write(" *\n");
			// TODO: Add code master version property
			bfw.write(" * @author CodeMaster v1.0\n");
			bfw.write(" */\n");
			bfw.write("public class " + className + " extends BeanFather implements Cloneable\n");
			bfw.write("{\n");

			// Writes the variables
			for (int i = 3; i < table.length; i++)
			{
				bfw.write("/**\n");

				// TODO: Add code master version property
				bfw.write(" * " + table[i][1]);
				System.out.println("/**");
				System.out.println(" * " + table[i][1]);
				System.out.println("*/");
				bfw.write(" */\n");
				String type = table[i][2];
				if (type.equals(""))
				{
					type = "byte[]";
				}
				bfw.write("\tprivate " + type + " " + table[i][0] + ";\n\n");

				System.out.println(" private " + type + " " + table[i][0] + ";");
			}
			bfw.write("\tprivate long beanNum;\n\n");

			// Writes default constructor
			bfw.write("\tpublic " + className + "()\n\t{\n\t\tinit();\n" + "\t\tbeanNum = super.getBeanId();\n\t}\n\n");

			// Writes the getters
			for (int i = 3; i < table.length; i++)
			{
				String type = table[i][2];
				if (type.equals(""))
				{
					type = "byte[]";
				}
				bfw.write(getGetter(table[i][0], table[i][1], type) + "\n");
			}

			bfw.write("\tpublic long getBeanNum()\n\t{\n\t\treturn this.beanNum;\n\t}\n\n");
			// Writes the init()
			bfw.write("\t/** Initializes the values */\n");
			bfw.write("\tpublic void init()\n\t{\n");
			for (int i = 3; i < table.length; i++)
			{
				bfw.write(getDefault(table[i][0], table[i][2]));
			}
			bfw.write("\t}\n\n");

			for (int i = 3; i < table.length; i++)
			{
				String type = table[i][2];
				if (type.equals(""))
				{
					type = "byte[]";
				}
				bfw.write(getSetter(table[i][0], table[i][1], type) + "\n");
			}

			bfw.write(setUpdater("put init into cacheMemory or db"));
			bfw.write(setInserter("insert this bean into cacheMemory or db"));
			bfw.write(setDepthClone("depth clone"));

			// Writes the toString()
			bfw.write("\t/** Returns the String representation */\n");
			bfw.write("\tpublic String toString()\n\t{\n");
			bfw.write("\t\treturn \"(" + className + ") \" \n\t\t\t");
			for (int i = 3; i < table.length; i++)
			{
				if (i == table.length - 1)
				{
					bfw.write("+ \"" + table[i][0] + "='\" + " + table[i][0] + " + \"'\"");
				}
				else
				{
					bfw.write("+ \"" + table[i][0] + "='\" + " + table[i][0] + " + \"', \"\n\t\t\t");
				}
			}
			bfw.write(";\n");
			bfw.write("\t}\n\n");

			// Writes the toCSVLine()
			bfw.write("\t/** Returns the CSV String */\n");
			bfw.write("\tpublic String toCSVLine()\n\t{\n");
			bfw.write("\t\treturn \n\t\t\t");
			for (int i = 3; i < table.length; i++)
			{
				if (i == table.length - 1)
				{
					bfw.write("\"\\\"\"+" + table[i][0] + "+\"\\\"\"");
				}
				else
				{
					bfw.write("\"\\\"\"+" + table[i][0] + "+\"\\\",\"+ \n\t\t\t");
				}
			}
			bfw.write(";\n");
			bfw.write("\t}\n\n");

			// // Writes the toCSVTitles()
			// bfw.write("\t/** Returns the String */\n");
			// bfw.write("\tpublic String toCSVTitles()\n\t{\n");
			// bfw.write("\t\t//TODO\n");
			// bfw.write("\t}\n\n");

			// Writes the closing parenthesis
			bfw.write("}");
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		finally
		{
			if (bfw != null)
			{
				try
				{
					bfw.close();
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}
		}

	}

	public void buildDomain(String header, File inputFile, File outputDir, String packageName)
	{
		// Determines the class name from the input file name
		String beanClassName = StringUtility.toTitleCase(FileUtility.getFileNameWithoutExtension(inputFile));
		String className = beanClassName.substring(0, beanClassName.length() - 4);

		BufferedWriter bfw = null;
		BufferedReader bfr = null;
		try
		{
			// Reads domain object attributes
			FileInputStream f = new FileInputStream(inputFile);
			Reader r = new InputStreamReader(f, "utf8");
			bfr = new BufferedReader(r);
			String[][] table = ExcelCSVParser.parse(bfr);
			bfr.close();

			// Creates the file at the destination folder
			String packageDir = Helper.getPackageDir(outputDir, packageName);
			(new File(packageDir)).mkdirs();
			bfw = new BufferedWriter(new FileWriter(packageDir + File.separator + className + ".java"));

			// Write header
			header = header.replaceAll("<class>", className);
			bfw.write(header);

			// Writes package
			bfw.write("package " + packageName + ";\n");
			bfw.write("\n");

			// Writes import
			bfw.write(getImport(table, "Domain"));
			bfw.write("\n");

			bfw.write("public class " + className);

			// + " implements CodeableData\n");
			bfw.write("{\n");

			bfw.write("\n");
			bfw.write("\tprivate " + beanClassName + " bean;");
			bfw.write("\n");
			bfw.write("\n");
			// Writes default constructor
			bfw.write("\tpublic " + className + "()\n\t{\n\t\t\n\t}\n\n");

			// Writes the getters
			for (int i = 3; i < table.length; i++)
			{
				String type = table[i][2];
				if (type.equals(""))
				{
					type = "byte[]";
				}
				bfw.write(getDomainGetter(table[i][0], table[i][1], type) + "\n");
			}

			for (int i = 3; i < table.length; i++)
			{
				String type = table[i][2];
				if (type.equals(""))
				{
					type = "byte[]";
				}
				bfw.write(getDomainSetter(table[i][0], table[i][1], type) + "\n");
			}

			// // Writes the toCSVTitles()
			// bfw.write("\t/** Returns the String */\n");
			// bfw.write("\tpublic String toCSVTitles()\n\t{\n");
			// bfw.write("\t\t//TODO\n");
			// bfw.write("\t}\n\n");

			// Writes the closing parenthesis
			bfw.write("}");
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		finally
		{
			if (bfw != null)
			{
				try
				{
					bfw.close();
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}
		}

	}

	/**
	 * Gets the default value for an attribute
	 * 
	 * @param name
	 *            attribute name
	 * @param type
	 *            attribute type
	 * @return the default values
	 */
	private String getCopyValue(String name, String type)
	{
		try
		{
			StringBuffer sb = new StringBuffer();
			if (type.equals("boolean"))
			{
				sb.append("\t\tthis." + name + " = source.is" + StringUtility.toTitleCase(name) + "();\n");
			}
			else
			{
				sb.append("\t\tthis." + name + " = source.get" + StringUtility.toTitleCase(name) + "();\n");
			}
			return sb.toString();
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.out.println(123);
		}
		return null;

	}

	/**
	 * Gets the setter method
	 * 
	 * @param name
	 *            attribute name
	 * @param description
	 *            attribute description
	 * @param type
	 *            attribute type
	 * @return the setter method
	 */
	private String getDefault(String name, String type)
	{
		// TODO: define constants to replaces hard coded type
		StringBuffer sb = new StringBuffer();
		if (type.equals("int"))
		{
			sb.append("\t\tthis." + name + " = 0;\n");
		}
		else if (type.equals("boolean"))
		{
			sb.append("\t\tthis." + name + " = true;\n");
		}
		else if (type.equals("String"))
		{
			sb.append("\t\tthis." + name + " = \"\";\n");
		}
		else if (type.equals("byte"))
		{
			sb.append("\t\tthis." + name + " = 0;\n");
		}
		else if (type.equals("short"))
		{
			sb.append("\t\tthis." + name + " = 0;\n");
		}
		else if (type.equals("long"))
		{
			sb.append("\t\tthis." + name + " = 0;\n");
		}
		else if (type.equals("Date"))
		{
			sb.append("\t\tthis." + name + " = new Date(0);\n");
		}
		else if (type.equals("float"))
		{
			sb.append("\t\tthis." + name + " = 0F;\n");
		}
		else if (type.equals("double"))
		{
			sb.append("\t\tthis." + name + " = 0D;\n");
		}
		else
		{
			sb.append("\t\tthis." + name + " = null;\n");
		}

		return sb.toString();
	}

	/**
	 * Gets the default value for an attribute
	 * 
	 * @param name
	 *            attribute name
	 * @param type
	 *            attribute type
	 * @return the default values
	 */
	private String getEquals(String name, String type, String className)
	{
		// TODO: replace hard coded value with constants
		// Ignores update date, update user, UID and new status.
		if (name.equals("updateDate") || name.equals("updateUser") || name.equalsIgnoreCase(className + "Uid") || name.equals("new"))
		{
			return "";
		}
		StringBuffer sb = new StringBuffer();
		if (type.equals("boolean"))
		{
			sb.append("\t\tif (this." + name + " != source.is" + StringUtility.toTitleCase(name) + "())\n\t\t{\n\t\t\treturn false;\n\t\t}\n");
		}
		else if (type.equals("int"))
		{
			sb.append("\t\tif (this." + name + " != source.get" + StringUtility.toTitleCase(name) + "())\n\t\t{\n\t\t\treturn false;\n\t\t}\n");
		}
		else
		{
			// Updated: Ken Yang 01-26-2006
			// To avoid null pointer exception, include null check when it's
			// not primitive types
			sb.append("\t\tif ((this.");
			sb.append(name);
			sb.append(" == null && source.get");
			sb.append(StringUtility.toTitleCase(name));
			sb.append("() != null)\n\t\t\t|| (this.");
			sb.append(name);
			sb.append(" != null && source.get");
			sb.append(StringUtility.toTitleCase(name));
			sb.append("() == null)\n\t\t\t|| (this.");
			sb.append(name);
			sb.append(" != null && source.get");
			sb.append(StringUtility.toTitleCase(name));
			sb.append("() != null\n\t\t\t\t&& !this.");
			sb.append(name);
			sb.append(".equals(source.get");
			sb.append(StringUtility.toTitleCase(name));
			sb.append("())))\n\t\t{\n\t\t\treturn false;\n\t\t}\n");
		}
		return sb.toString();
	}

	/**
	 * Gets the getter method
	 * 
	 * @param name
	 *            attribute name
	 * @param description
	 *            attribute description
	 * @param type
	 *            attribute type
	 * @return the getter method
	 */
	private String getGetter(String name, String description, String type)
	{
		StringBuffer sb = new StringBuffer();
		sb.append("\t/** Gets " + description + " */\n");
		if (type.equals("boolean"))
		{
			sb.append("\tpublic " + type + " is" + StringUtility.toTitleCase(name) + "()\n\t{\n");
		}
		else
		{
			sb.append("\tpublic " + type + " get" + StringUtility.toTitleCase(name) + "()\n\t{\n");
		}
		sb.append("\t\treturn this." + name + ";\n");
		sb.append("\t}\n");
		return sb.toString();
	}

	/**
	 * Gets the getter method
	 * 
	 * @param name
	 *            attribute name
	 * @param description
	 *            attribute description
	 * @param type
	 *            attribute type
	 * @return the getter method
	 */
	private String getDomainGetter(String name, String description, String type)
	{
		StringBuffer sb = new StringBuffer();
		sb.append("\t/** Gets " + description + " */\n");
		if (type.equals("boolean"))
		{
			sb.append("\tpublic " + type + " is" + StringUtility.toTitleCase(name) + "()\n\t{\n");
		}
		else
		{
			sb.append("\tpublic " + type + " get" + StringUtility.toTitleCase(name) + "()\n\t{\n");
		}
		sb.append("\t\treturn bean.get" + StringUtility.toTitleCase(name) + "();\n");
		sb.append("\t}\n");
		return sb.toString();
	}

	/**
	 * Gets the import statements
	 * 
	 * @param table
	 *            attribute table
	 * @return the import statements
	 */
	private String getImport(String[][] table, String str)
	{
		boolean[] isImported = new boolean[imports.length];
		// Always import Serializable
		isImported[0] = true;

		StringBuffer sb = new StringBuffer();
		for (int i = 3; i < table.length; i++)
		{
			System.out.print(i);
			if (table[i][2].equalsIgnoreCase("BigDecimal"))
			{

				isImported[1] = true;
			}
			if (table[i][2].equalsIgnoreCase("Timestamp"))
			{
				isImported[2] = true;
			}
			if (table[i][2].equalsIgnoreCase("Date"))
			{
				isImported[3] = true;
			}
			if (str.equals("Bean"))
			{
				isImported[4] = true;
				isImported[5] = true;
				isImported[6] = true;
				isImported[7] = true;
				isImported[8] = true;
				isImported[9] = true;
				isImported[10] = true;
				isImported[11] = true;
			}
			else
			{
				isImported[4] = false;
				isImported[5] = false;
			}
		}
		for (int i = 3; i < isImported.length; i++)
		{
			if (isImported[i])
			{
				sb.append(imports[i]);
			}
		}
		return sb.toString();

	}

	/**
	 * Gets the setter method
	 * 
	 * @param name
	 *            attribute name
	 * @param description
	 *            attribute description
	 * @param type
	 *            attribute type
	 * @return the setter method
	 */
	private String getSetter(String name, String description, String type)
	{
		StringBuffer sb = new StringBuffer();
		sb.append("\t/** Sets " + description + " */\n");
		sb.append("\tpublic void set" + StringUtility.toTitleCase(name) + "(" + type + " " + name + ")\n\t{\n");
		sb.append("\t\tthis." + name + " = " + name + ";\n");
		// sb.append("\t\tDBService.getInstance().update(beanNum, this.getClass(), this);\n");
		sb.append("\t}\n");
		return sb.toString();
	}

	/**
	 * put this bean data into cache
	 * 
	 * @param description
	 * @return
	 */
	private String setUpdater(String description)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("\t/** Sets " + description + " **/ \n");
		sb.append("\tpublic void update()\n\t{\n");
		sb.append("\t\tDBService.getInstance().update(beanNum, this.getClass(), this);\n");
		sb.append("\t}\n");
		return sb.toString();
	}

	/**
	 * insert this bean into cache
	 * 
	 * @return
	 */
	private String setInserter(String description)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("\t/** Sets " + description + " **/ \n");
		sb.append("\tpublic void insert()\n\t{\n");
		sb.append("\t\tDBService.getInstance().insert(beanNum, this.getClass(), this);\n");
		sb.append("\t}\n");
		return sb.toString();
	}

	/**
	 * clone
	 * 
	 * @param description
	 * @return
	 */
	private String setClone(String description)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("\t/** " + description + " **/ \n");
		sb.append("\tpublic Object clone()\n\t{\n");
		sb.append("\t\tObject obj = null;\n");
		sb.append("\t\ttry{\n");
		sb.append("\t\t\tobj = super.clone();\n");
		sb.append("\t\t} catch (CloneNotSupportedException e) { \n");
		sb.append("\t\t\te.printStackTrace();\n");
		sb.append("\t\t}\n");
		sb.append("\t\treturn obj;\n");
		sb.append("\t}\n");
		return sb.toString();
	}
	
	/**
	 * depth clone
	 * 
	 * @param description
	 * @return
	 */
	private String setDepthClone(String description)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("\t/** " + description + " **/ \n");
		sb.append("\tpublic Object clone()\n\t{\n");
		sb.append("\t\ttry{\n");
		sb.append("\t\t\tByteArrayOutputStream bos = new ByteArrayOutputStream();\n");
		sb.append("\t\t\tObjectOutputStream oos = new ObjectOutputStream(bos);\n");
		sb.append("\t\t\toos.writeObject(this);\n");
		sb.append("\t\t\tByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());\n");
		sb.append("\t\t\tObjectInputStream ois = new ObjectInputStream(bis);\n");
		sb.append("\t\t\treturn ois.readObject();\n");
		sb.append("\t\t} catch (IOException | ClassNotFoundException e) { \n");
		sb.append("\t\t\te.printStackTrace();\n");
		sb.append("\t\t\treturn null;\n");
		sb.append("\t\t}\n");
		sb.append("\t}\n");
		return sb.toString();
	}

	private String getDomainSetter(String name, String description, String type)
	{
		StringBuffer sb = new StringBuffer();
		sb.append("\t/** Sets " + description + " */\n");
		sb.append("\tvoid set" + StringUtility.toTitleCase(name) + "(" + type + " " + name + ")\n\t{\n");
		sb.append("\t\tbean.set" + StringUtility.toTitleCase(name) + "(" + name + ");\n");
		sb.append("\t}\n");
		return sb.toString();
	}

	/**
	 * Gets the entity ID based on unique constraint
	 * 
	 * @param tableName
	 *            table name
	 * @param table
	 *            table attributes
	 * @return the script for the table unique constraint
	 */
	private String getUniqueId(String className, String[][] table)
	{
		StringBuffer sb = new StringBuffer();
		boolean isFirst = true;
		boolean hasUniqueKey = false;
		sb.append("\tpublic String getUniqueId()\n");
		sb.append("\t{\n");

		for (int i = 3; i < table.length; i++)
		{
			if ((table[i][8]).equalsIgnoreCase(YES) && (!(table[i][C_NAME]).equalsIgnoreCase("companyUid")))
			{
				hasUniqueKey = true;
				if (isFirst)
				{
					sb.append("\t\treturn \"" + className + " \"+ this." + table[i][C_NAME]);
				}
				else
				{
					sb.append(" + \", \" + this." + table[i][C_NAME]);
				}
				isFirst = false;
			}
		}
		if (!hasUniqueKey)
		{
			sb.append("\t\treturn null");
		}
		sb.append(";\n\t}\n");
		return sb.toString();
	}

}
